package com.example.quartz.util;

import com.example.common.constant.Constants;
import com.example.common.constant.ScheduleConstants;
import com.example.common.utils.ExceptionUtil;
import com.example.common.utils.StringUtils;
import com.example.common.utils.bean.BeanUtils;
import com.example.common.utils.spring.SpringUtils;
import com.example.quartz.domain.SysJob;
import com.example.quartz.domain.SysJobLog;
import com.example.quartz.service.ISysJobLogService;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;

/**
 * 抽象quartz调用
 * - 实现`Job`接口
 * - 提供执行前、执行后、异常处理的统一处理
 * - 记录执行日志到数据库
 */
public abstract class AbstractQuartzJob implements Job {
    /**
     * 日志记录器
     * sealed是Java 17中的一个新特性，它可以用来限制类的继承。
     * 这里我们将AbstractQuartzJob类声明为sealed，
     * 这意味着只有它的直接子类可以继承它。
     * 直接子类是什么？
     * 直接子类是那些继承自AbstractQuartzJob类的类。
     * 比如：
     * public class MyQuartzJob extends AbstractQuartzJob {
     * // 实现execute方法
     * }
     * 其他类不能继承AbstractQuartzJob类。
     * 这是为了确保所有的quartz任务都有一个统一的执行入口，
     * 同时也避免了一些潜在的问题，比如：
     * - 不同的任务有不同的执行逻辑，但是都继承自AbstractQuartzJob类，
     * 这会导致一些不可预期的问题。
     * - 不同的任务有不同的执行时间，但是都继承自AbstractQuartzJob类，
     * 这会导致一些不可预期的问题。
     */
    private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class);

    /**
     * 线程本地变量
     */
    private static ThreadLocal<Date> threadLocal = new ThreadLocal<>();

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        SysJob sysJob = new SysJob();
        BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));
        try {
            before(context, sysJob);
            if (sysJob != null) {
                doExecute(context, sysJob);
            }
            after(context, sysJob, null);
        } catch (Exception e) {
            log.error("任务执行异常   -  {}", sysJob.getJobName(), e);
            after(context, sysJob, e);
        }
    }

    /**
     * 执行前
     *
     * @param context 工作执行上下文对象
     * @param sysJob  系统计划任务
     */
    protected void before(JobExecutionContext context, SysJob sysJob) {
        // 记录执行开始时间
        threadLocal.set(new Date());
    }

    /**
     * 执行后
     *
     * @param context 工作执行上下文对象
     * @param sysJob  系统计划任务
     * @param e       异常对象
     */
    protected void after(JobExecutionContext context, SysJob sysJob, Exception e) {
        Date startTime = threadLocal.get();
        threadLocal.remove();

        final SysJobLog sysJobLog = new SysJobLog();
        sysJobLog.setJobName(sysJob.getJobName());
        sysJobLog.setJobGroup(sysJob.getJobGroup());
        sysJobLog.setInvokeTarget(sysJob.getInvokeTarget());
        sysJobLog.setStartTime(startTime);
        sysJobLog.setStopTime(new Date());
        long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime();
        sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时: " + runMs + "ms");
        // 处理异常情况
        if (e != null) {
            // 记录失败状态
            sysJobLog.setStatus(Constants.FAIL);
            // 拿到异常信息
            String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000);
            // 记录异常信息
            sysJobLog.setExceptionInfo(errorMsg);
        } else {
            // 记录成功状态
            sysJobLog.setStatus(Constants.SUCCESS);
        }
        SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog);
    }

    /**
     * 执行方法，由子类重写实现
     *
     * @param context 工作执行上下文对象
     * @param sysJob  系统计划任务
     * @throws Exception 执行时可能抛出的异常
     */
    protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception;
}
